﻿/*
 * Copyright (c) Seagull Consulting, Inc. All rights reserved. See License.txt in the project root for license information.
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Xml;
using SeagullConsulting.MvcAjaxControls.Web.Mvc.Helpers.Properties;

namespace SeagullConsulting.MvcAjaxControls.Web.Mvc.Helpers
{
    public enum ColumnFormats
    {
        ShortDate,
        DateTime,
        Time,
        Currency,
        String
    }

    #region Interfaces
    /// <summary>
    /// Properties and methods used by the consumer to configure the AjaxGridBuilder.
    /// </summary>
    public interface IAjaxGridBuilder<T> where T : class
    {
        AjaxGridBuilder<T> Controller<TController>() where TController : class;
        AjaxGridBuilder<T> FromDataSource(string dataSource);
        AjaxGridBuilder<T> WithColumns(Action<ColumnBuilder<T>> columnBuilder);
        AjaxGridBuilder<T> WithActionColumns(Action<ActionColumnBuilder<T>> columnbuilder);
        AjaxGridBuilder<T> WithPaging(int pageSize);
        AjaxGridBuilder<T> WithFiltering(bool filterEnabled, string filterSource);
        MvcHtmlString ToHtml();
    }
    #endregion

    #region TableFor Extension
    public static class MvcHtmlTableExtensions
    {
        /// <summary>
        /// Return an instance of a AjaxGridBuilder.
        /// </summary>
        /// <typeparam name="TModel">Type of model to render in the table.</typeparam>
        /// <returns>Instance of a AjaxGridBuilder.</returns>
        public static IAjaxGridBuilder<T> AjaxGridFor<T>(this HtmlHelper helper) where T : class
        {
            return new AjaxGridBuilder<T>(helper);
        }
    }
    #endregion

    #region AjaxGridBuilder
    public class AjaxGridBuilder<T> : IAjaxGridBuilder<T>
        where T : class
    {
        private HtmlHelper htmlHelper { get; set; }
        private string controller = String.Empty;
        private string dataSource = String.Empty;
        private bool pagerEnabled = false;
        private bool filterEnabled = false;
        private string filterSource = String.Empty;
        private int pageSize = 0;

        #region ctor
        /// <summary>
        /// Default constructor.
        /// </summary>
        private AjaxGridBuilder()
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        internal AjaxGridBuilder(HtmlHelper helper)
        {
            htmlHelper = helper;
            GridColumns = new List<IGridColumn<T>>();
            GridActionColumns = new List<IGridActionColumn<T>>();
            //GridActionColumns = new List<MvcHtmlString>();
            if ((string)helper.ViewData["controller"] != String.Empty)
                controller = helper.ViewData["controller"].ToString().Replace("Controller", String.Empty);
        }
        #endregion

        #region Controller
        /// <summary>
        /// Set the name of the controller for the AjaxGrid.
        /// Note: You can explicitly set the Controller or just set it once in the ViewData
        /// </summary>
        /// <param name="Controller">MVC controller for the AjaxGrid.</param>
        /// <returns>Reference to the AjaxGridBuilder object.</returns>
        public AjaxGridBuilder<T> Controller<TController>()
            where TController : class
        {
            var type = typeof(TController);
            this.controller = type.Name.Replace("Controller", String.Empty);
            return this;
        }
        #endregion

        #region WithColumns
        /// <summary>
        /// Create an instance of the ColumnBuilder to add columns to the table.
        /// </summary>
        /// <param name="columnBuilder">Delegate to create an instance of ColumnBuilder.</param>
        /// <returns>An instance of AjaxGridBuilder.</returns>
        public AjaxGridBuilder<T> WithColumns(Action<ColumnBuilder<T>> columnBuilder)
        {
            ColumnBuilder<T> builder = new ColumnBuilder<T>(this);
            columnBuilder(builder);
            return this;
        }
        #endregion

        #region WithActionColumns
        /// <summary>
        /// Create an instance of the ColumnBuilder to add columns to the table.
        /// </summary>
        /// <param name="columnBuilder">Delegate to create an instance of ColumnBuilder.</param>
        /// <returns>An instance of AjaxGridBuilder.</returns>
        public AjaxGridBuilder<T> WithActionColumns(Action<ActionColumnBuilder<T>> columnBuilder)
        {
            ActionColumnBuilder<T> builder = new ActionColumnBuilder<T>(this);
            columnBuilder(builder);
            return this;
        }
        #endregion

        #region FromDataSource
        /// <summary>
        /// Set the enumerable list of model objects.
        /// </summary>
        /// <param name="dataSource">Enumerable list of model objects.</param>
        /// <returns>Reference to the AjaxGridBuilder object.</returns>
        public AjaxGridBuilder<T> FromDataSource(string dataSource)
        {
            this.dataSource = dataSource;
            return this;
        }
        #endregion

        #region WithPaging
        /// <summary>
        /// Set the action for the pager element and the default page size.
        /// </summary>
        /// <param name="Pager">Pager control for the AjaxGrid.</param>
        /// <returns>Reference to the AjaxGridBuilder object.</returns>
        public AjaxGridBuilder<T> WithPaging(int pageSize)
        {
            this.pagerEnabled = true;
            this.pageSize = pageSize;
            return this;
        }
        #endregion

        #region WithFiltering
        /// <summary>
        /// Set the grid for the displaying filtering.
        /// </summary>
        /// <param name="Pager">Filter control for the AjaxGrid.</param>
        /// <returns>Reference to the AjaxGridBuilder object.</returns>
        public AjaxGridBuilder<T> WithFiltering(bool filterEnabled, string filterSource)
        {
            this.filterEnabled = true;
            this.filterSource = filterSource;
            return this;
        }
        #endregion

        #region ToHtml
        /// <summary>
        /// Convert the AjaxGridBuilder to HTML.
        /// </summary>
        public MvcHtmlString ToHtml()
        {
            StringBuilder html = new StringBuilder();
            StringBuilder scriptTemplateSB = new StringBuilder();

            //Start building the AjaxGrid
            TagBuilder grid = new TagBuilder("table");
            StringBuilder gridSB = new StringBuilder();
            grid.Attributes["id"] = "mainList";
            grid.AddCssClass("ajaxGrid");

            //Build the grid header and attach to grid
            gridSB.Append(BuildGridHeader());

            //Build the empty grid body and attach to the grid
            TagBuilder gBody = new TagBuilder("tbody");
            StringBuilder tBodySB = new StringBuilder();
            StringBuilder tBodyRowSB = new StringBuilder();
            gBody.InnerHtml = String.Empty;
            gridSB.Append(gBody);
            grid.InnerHtml = gridSB.ToString();

            //Add the grid to the output
            html.Append(grid);
            html.Append(Environment.NewLine);

            //If Pager is configured, then attach the pager control to the output
            if (pagerEnabled)
            {
                html.Append(Resources.AjaxGridPager);
                html.Append(Environment.NewLine);
            }

            //Build script template for the data rows
            TagBuilder scriptTemplate = new TagBuilder("script");
            scriptTemplate.Attributes["id"] = "listTemplate";
            scriptTemplate.Attributes["type"] = "text/x-jsrender";

            //Add script template to the output
            scriptTemplate.InnerHtml = BuildScriptTemplate();
            html.Append(scriptTemplate);
            html.Append(Environment.NewLine);

            //Build main AjaxGrid JQuery script
            TagBuilder scriptMain = new TagBuilder("script");
            scriptMain.Attributes["type"] = "text/javascript";
            StringBuilder scriptSB = new StringBuilder();
            String scriptText = Resources.AjaxGridScript;
            scriptSB.Append(scriptText);

            //Add filter, paging parameters, and show grid sections to script
            scriptText = Resources.AjaxGridFilterEventsScript;
            scriptText = scriptText.Replace("$dataSource", filterSource);
            scriptSB.Append(scriptText);
            scriptSB.Append(Environment.NewLine);
            scriptText = Resources.AjaxGridPagingEventsScript;
            scriptText = scriptText.Replace("$dataSource", dataSource);
            scriptText = scriptText.Replace("$pageSize", pageSize.ToString());
            scriptSB.Append(scriptText);
            scriptSB.Append(Environment.NewLine);
            scriptText = Resources.AjaxGridShowScript;
            scriptSB.Append(scriptText);
            scriptSB.Append(Environment.NewLine);
            scriptSB.Append("});");
            scriptSB.Append(Environment.NewLine);
            scriptMain.InnerHtml = Environment.NewLine + scriptSB.ToString();

            //Add main AjaxGrid JQuery script to output
            html.Append(scriptMain);

            return new MvcHtmlString(html.ToString());
        }

        #region Build Grid Header
        //Build Table Head
        private string BuildGridHeader()
        {
            TagBuilder gHead = new TagBuilder("thead");
            StringBuilder gHeadSB = new StringBuilder();
            TagBuilder gHeadRow = new TagBuilder("tr");
            StringBuilder gHeaderSB = new StringBuilder();
            StringBuilder gHeadRowSB = new StringBuilder();

            //Build the headers for the data columns
            foreach (IGridColumn<T> gc in GridColumns)
            {
                TagBuilder gHeadRowTd = new TagBuilder("th");
                gHeadRowTd.AddCssClass(gc.Format.ToString());
                gHeadRowTd.InnerHtml = gc.Header;
                gHeadRowSB.Append(gHeadRowTd);
            }

            //Build the headers for the action columns
            foreach (IGridActionColumn<T> gac in GridActionColumns)
            {
                TagBuilder gHeadRowTd = new TagBuilder("th");
                TagBuilder scriptTemplateRowTd = new TagBuilder("td");
                gHeadRowTd.InnerHtml = String.Empty;
                gHeadRowSB.Append(gHeadRowTd);
            }

            //Finalize the grid data header
            gHeadRow.InnerHtml = gHeadRowSB.ToString();
            gHeadSB.Append(gHeadRow);
            gHeadSB.Append(Environment.NewLine);

            //Build a header row for the filter control, if enabled
            if (filterEnabled)
            {
                TagBuilder gHeadFilterRow = new TagBuilder("tr");
                StringBuilder gHeadFilterRowSB = new StringBuilder();

                //Build the filters for the data columns
                foreach (IGridColumn<T> gc in GridColumns)
                {
                    TagBuilder gFilterRowTd = new TagBuilder("th");
                    gFilterRowTd.AddCssClass(gc.Format.ToString());
                    if (gc.FilterEnabled)
                    {
                        TagBuilder gFilterInput = new TagBuilder("input");
                        gFilterInput.AddCssClass("filterInput");
                        gFilterInput.Attributes["id"] = gc.PropertyName + "_Filter";
                        gFilterRowTd.InnerHtml = gFilterInput.ToString();
                    }
                    gHeadFilterRowSB.Append(gFilterRowTd);
                }

                //Build blank filters for the action columns
                //Steal first action column for filter image
                if (GridActionColumns.Count() > 0)
                {
                    int idx = 0;
                    foreach (IGridActionColumn<T> gac in GridActionColumns)
                    {
                        TagBuilder gFilterRowTd = new TagBuilder("th");
                        if (idx == 0)
                        {
                            gFilterRowTd.AddCssClass("FilterImage");
                            gFilterRowTd.Attributes["id"] = "filterButton";
                        }
                        gFilterRowTd.InnerHtml = String.Empty;
                        gHeadFilterRowSB.Append(gFilterRowTd);
                        idx++;
                    }
                }
                //Or, if no action columns then create and empty column for filter image
                else
                {
                    TagBuilder gFilterRowTd = new TagBuilder("th");
                    gFilterRowTd.AddCssClass("FilterImage");
                    gFilterRowTd.Attributes["id"] = "filterButton";
                    gFilterRowTd.InnerHtml = String.Empty;
                    gHeadFilterRowSB.Append(gFilterRowTd);                    
                }
                gHeadFilterRow.InnerHtml = gHeadFilterRowSB.ToString();
                gHeadSB.Append(gHeadFilterRow.ToString());
                gHeadSB.Append(Environment.NewLine);
            }

            gHead.InnerHtml = gHeadSB.ToString();
            gHeaderSB.Append(gHead);
            gHeaderSB.Append(Environment.NewLine);
            return gHeaderSB.ToString();
        }
        #endregion

        #region Build Script Template
        //Build Script Template
        private string BuildScriptTemplate()
        {
            TagBuilder scriptTemplateRow = new TagBuilder("tr");
            StringBuilder scriptTemplateRowSB = new StringBuilder();

            //Build the jsrender template for each data column
            foreach (IGridColumn<T> gc in GridColumns)
            {
                TagBuilder scriptTemplateRowTd = new TagBuilder("td");
                scriptTemplateRowTd.AddCssClass(gc.Format.ToString());
                switch (gc.Format)
                {
                    case ColumnFormats.ShortDate:
                        {
                            scriptTemplateRowTd.InnerHtml = "{{>~format(" + gc.PropertyName + ", 'shortdate')}}";
                            break;
                        }
                    case ColumnFormats.DateTime:
                        {
                            scriptTemplateRowTd.InnerHtml = "{{>~format(" + gc.PropertyName + ", 'datetime')}}";
                            break;
                        }
                    case ColumnFormats.Time:
                        {
                            scriptTemplateRowTd.InnerHtml = "{{>~format(" + gc.PropertyName + ", 'time')}}";
                            break;
                        }
                    case ColumnFormats.Currency:
                        {
                            scriptTemplateRowTd.InnerHtml = "{{>~format(" + gc.PropertyName + ", 'currency')}}";
                            break;
                        }
                    case ColumnFormats.String:
                        {
                            scriptTemplateRowTd.InnerHtml = "{{:" + gc.PropertyName + "}}";
                            break;
                        }
                }

                scriptTemplateRowSB.Append(scriptTemplateRowTd + Environment.NewLine);
            }
            //Build the jsrender template for action columns
            foreach (IGridActionColumn<T> gac in GridActionColumns)
            {
                TagBuilder scriptTemplateRowTd = new TagBuilder("td");
                TagBuilder anchorLink = new TagBuilder("a");
                //Handle if controller specified for action column
                if (gac.Controller == String.Empty)
                    anchorLink.Attributes["href"] = "/" + controller + "/" + gac.Action + "/" + "{{:" + gac.Id + "}}";
                else
                    anchorLink.Attributes["href"] = "/" + gac.Controller + "/" + gac.Action + "/" + "{{:" + gac.Id + "}}";

                //Handle if it's an ActionLink or an ImageActionLink
                if (gac.ImageUrl.Length == 0)
                    //ActionLink
                    anchorLink.InnerHtml = gac.Text;
                else
                {
                    //ImageActionLink
                    anchorLink.Attributes["title"] = gac.Text;
                    TagBuilder image = new TagBuilder("img");
                    image.AddCssClass("actionLink");
                    image.Attributes["alt"] = gac.Text;
                    image.Attributes["src"] = gac.ImageUrl;
                    anchorLink.InnerHtml = image.ToString();
                }
                scriptTemplateRowTd.InnerHtml = anchorLink.ToString();
                scriptTemplateRowSB.Append(scriptTemplateRowTd);
                scriptTemplateRowSB.Append(Environment.NewLine);
            }

            //Build the script templet
            scriptTemplateRow.InnerHtml = scriptTemplateRowSB.ToString();
            return scriptTemplateRow.ToString() + Environment.NewLine;

        }
        #endregion

        #endregion

        #region Internal column list management
        /// <summary>
        /// List of AjaxGrid columns.
        /// </summary>
        internal IList<IGridColumn<T>> GridColumns { get; set; }
        internal IList<IGridActionColumn<T>> GridActionColumns { get; set; }
        //internal IList<MvcHtmlString> GridActionColumns { get; set; }

        /// <summary>
        /// Add an lambda expression as a TableColumn.
        /// </summary>
        /// <typeparam name="TProp">Model class property to be added as a column.</typeparam>
        /// <param name="expression">Lambda expression identifying a property to be rendered.</param>
        /// <returns>An instance of TableColumn.</returns>
        internal IGridColumn<T> AddColumn<TProp>(string header, Expression<Func<T, TProp>> expression, ColumnFormats format, bool filterEnabled)
        {
            GridColumn<T, TProp> column = new GridColumn<T, TProp>(header, expression, format, filterEnabled);
            this.GridColumns.Add(column);
            return column;
        }
        internal IGridColumn<T> AddColumn<TProp>(Expression<Func<T, TProp>> expression, ColumnFormats format, bool filterEnabled)
        {
            GridColumn<T, TProp> column = new GridColumn<T, TProp>(expression, format, filterEnabled);
            this.GridColumns.Add(column);
            return column;
        }
        internal IGridActionColumn<T> AddActionColumn<TProp>(string text, string action, Expression<Func<T, TProp>> expression)
        {
            GridActionColumn<T, TProp> column = new GridActionColumn<T, TProp>(text, action, expression);
            this.GridActionColumns.Add(column);
            return column;
        }
        internal IGridActionColumn<T> AddActionColumn<TProp>(string text, string action, string controller, Expression<Func<T, TProp>> expression)
        {
            GridActionColumn<T, TProp> column = new GridActionColumn<T, TProp>(text, action, controller, expression);
            this.GridActionColumns.Add(column);
            return column;
        }
        internal IGridActionColumn<T> AddActionColumn<TProp>(string text, string action, Expression<Func<T, TProp>> expression, string imageUrl)
        {
            GridActionColumn<T, TProp> column = new GridActionColumn<T, TProp>(text, action, expression, imageUrl);
            this.GridActionColumns.Add(column);
            return column;
        }
        internal IGridActionColumn<T> AddActionColumn<TProp>(string text, string action, string controller, Expression<Func<T, TProp>> expression, string imageUrl)
        {
            GridActionColumn<T, TProp> column = new GridActionColumn<T, TProp>(text, action, controller, expression, imageUrl);
            this.GridActionColumns.Add(column);
            return column;
        }

        #endregion

    }
    #endregion
}
